﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Query Application Block
//===============================================================================

using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using EntLibContrib.Query.Configuration;
using EntLibContrib.Query.Parameters;
using EntLibContrib.Query.Properties;

namespace EntLibContrib.Query.Commands
{
	/// <summary>
	/// Simple class to hold the components of a data command
	/// </summary>
	[ConfigurationElementType(typeof(DataCustomCommandData))]
	public class DataCommand : Command
	{
		#region Fields
		private Database database;
		private string command;
		private CommandType commandType;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the database.
		/// </summary>
		/// <value>The database.</value>
		public Database Database
		{
			get { return database; }
			set { database = value; }
		}

		/// <summary>
		/// Gets the command.
		/// </summary>
		/// <value>The command.</value>
		public string Command
		{
			get { return command; }
			set { command = value; }
		}

		/// <summary>
		/// Gets the command type.
		/// </summary>
		/// <value>The command type.</value>
		public CommandType CommandType
		{
			get { return commandType; }
			set { commandType = value; }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new instance of the <see cref="DataCommand"/> class.
		/// </summary>
		protected DataCommand()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataCommand"/> class.
		/// </summary>
		/// <param name="database">The database.</param>
		/// <param name="command">The command.</param>
		/// <param name="commandType">Type of the command.</param>
		/// <param name="parameters">The parameters.</param>
		public DataCommand(Database database, string command, CommandType commandType, ParameterDictionary parameters)
			: base(parameters)
		{
			ValidateAndStoreParameters(database, command, commandType);
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Executes a read action.
		/// </summary>
		/// <returns>
		/// a DataSet containing the results of the read action
		/// </returns>
		public override DataSet ExecuteForRead()
		{
			// build the database command with parameters and their values
			DbCommand dbCommand = BuildDbCommand();

			// execute the read action
			DataSet results = database.ExecuteDataSet(dbCommand);

			// feedback any output parameters
			GetParameterValues(dbCommand);

			// return the results of the read action
			return results;
		}

		/// <summary>
		/// Executes a write action.
		/// </summary>
		public override void ExecuteForWrite()
		{
			// build the database command with parameters and their values
			DbCommand dbCommand = BuildDbCommand();

			// execute the write action
			database.ExecuteNonQuery(dbCommand);

			// feedback any output parameters
			GetParameterValues(dbCommand);
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Validates the and store parameters.
		/// </summary>
		/// <param name="databaseInstance">The database instance.</param>
		/// <param name="databaseCommand">The database command.</param>
		/// <param name="databaseCommandType">Type of the database command.</param>
		private void ValidateAndStoreParameters(Database databaseInstance, string databaseCommand, CommandType databaseCommandType)
		{
			// check for a null database
			if (databaseInstance == null)
				throw new ArgumentNullException("databaseInstance", InternalResources.ExceptionDatabaseInstanceNull);

			// check for a null or empty command
			if (string.IsNullOrEmpty(databaseCommand))
				throw new ArgumentNullException("databaseCommand", InternalResources.ExceptionDatabaseCommandNullOrEmpty);

			// store the parameters
			this.database = databaseInstance;
			this.command = databaseCommand;
			this.commandType = databaseCommandType;
		}

		/// <summary>
		/// Builds a generic <see cref="DbCommand"/> object.
		/// </summary>
		/// <returns>A <see cref="DbCommand"/> object.</returns>
		private DbCommand BuildDbCommand()
		{
			// create a generic database command
			DbCommand dbCommand;
			switch (commandType)
			{
				case CommandType.StoredProcedure:
					dbCommand = database.GetStoredProcCommand(command);
					break;
				case CommandType.Text:
					dbCommand = database.GetSqlStringCommand(command);
					break;
				case CommandType.TableDirect:
					dbCommand = database.GetSqlStringCommand("SELECT * FROM " + command);
					break;
				default:
					dbCommand = database.GetStoredProcCommand(command);
					break;
			}

			// add the parameters
			foreach (KeyValuePair<string, IParameter> parameterPair in Parameters)
			{
				DataSetParameter dataSetParameter = parameterPair.Value as DataSetParameter;
				if (dataSetParameter != null)
					database.AddParameter(dbCommand, dataSetParameter.Name, dataSetParameter.DbType, SetParameterSize(dataSetParameter.Direction), dataSetParameter.Direction, SetNullable(dataSetParameter.Direction), 0, 0, dataSetParameter.Column, dataSetParameter.Version, dataSetParameter.Value);
				else
				{
					DataParameter dataParameter = parameterPair.Value as DataParameter;
					if (dataParameter != null)
						database.AddParameter(dbCommand, dataParameter.Name, dataParameter.DbType, SetParameterSize(dataParameter.Direction), dataParameter.Direction, SetNullable(dataParameter.Direction), 0, 0, string.Empty, DataRowVersion.Default, dataParameter.Value);
					else
					{
						Parameter parameter = parameterPair.Value as Parameter;
						if (parameter != null)
						{
							database.AddParameter(dbCommand, parameter.Name, DbType.String, SetParameterSize(ParameterDirection.Input), ParameterDirection.Input, SetNullable(ParameterDirection.Input), 0, 0, string.Empty, DataRowVersion.Default, parameter.Value);
						}
					}
				}
			}

			return dbCommand;
		}

		/// <summary>
		/// Gets the parameter values returned from the command execution
		/// and feeds the values back into the parameter dictionary.
		/// </summary>
		/// <remarks>this action will retrieve any output parameter values</remarks>
		/// <param name="dbCommand">The db command.</param>
		private void GetParameterValues(DbCommand dbCommand)
		{
			foreach (KeyValuePair<string, IParameter> parameter in Parameters)
			{
				parameter.Value.Value = database.GetParameterValue(dbCommand, parameter.Key);
			}
		}

		/// <summary>
		/// Sets the size of the parameter depending on the parameter direction.
		/// </summary>
		/// <param name="direction">The parameter direction.</param>
		/// <returns>an integer value</returns>
		private static int SetParameterSize(ParameterDirection direction)
		{
			switch (direction)
			{
				case ParameterDirection.Input:
					return 0;
				case ParameterDirection.InputOutput:
					return int.MaxValue;
				case ParameterDirection.Output:
					return int.MaxValue;
				case ParameterDirection.ReturnValue:
					return 0;
				default:
					return 0;
			}
		}

		/// <summary>
		/// Sets the nullable value depending on the parameter direction.
		/// </summary>
		/// <param name="direction">The parameter direction.</param>
		/// <returns>true if the parameter must be nullable or false if not</returns>
		private static bool SetNullable(ParameterDirection direction)
		{
			switch (direction)
			{
				case ParameterDirection.Input:
					return false;
				case ParameterDirection.InputOutput:
					return false;
				case ParameterDirection.Output:
					return true;
				case ParameterDirection.ReturnValue:
					return true;
				default:
					return false;
			}
		}
		#endregion
	}
}
